const { dbCmd, userCollection } = require('../../common/constants');
const { ERROR } = require('../../common/error');
/**
 * 获取随机邀请码，邀请码由大写字母加数字组成，由于存在手动输入邀请码的场景，从可选字符中去除 0、1、I、O
 * @param {number} len 邀请码长度，默认6位
 * @returns {string} 随机邀请码
 */
function getRandomInviteCode(len = 6) {
    const charArr = [
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'J',
        'K',
        'L',
        'M',
        'N',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
    ];
    let code = '';
    for (let i = 0; i < len; i++) {
        code += charArr[Math.floor(Math.random() * charArr.length)];
    }
    return code;
}

/**
 * 获取可用的邀请码，至多尝试十次以获取可用邀请码。从10亿可选值中随机，碰撞概率较低
 * 也有其他方案可以尝试，比如在数据库内设置一个从0开始计数的数字，每次调用此方法时使用updateAndReturn使数字加1并返回加1后的值，根据这个值生成对应的邀请码，比如（22222A + 1 == 22222B），此方式性能理论更好，但是不适用于旧项目
 * @param {object} param
 * @param {string} param.inviteCode 初始随机邀请码
 */
async function getValidInviteCode() {
    let retry = 10;
    let code;
    let codeValid = false;
    while (retry > 0 && !codeValid) {
        retry--;
        code = getRandomInviteCode();
        const getUserRes = await userCollection
            .where({
                my_invite_code: code,
            })
            .limit(1)
            .get();
        if (getUserRes.data.length === 0) {
            codeValid = true;
            break;
        }
    }
    if (!codeValid) {
        throw {
            errCode: ERROR.SET_INVITE_CODE_FAILED,
        };
    }
    return code;
}

/**
 * 根据邀请码查询邀请人
 * @param {object} param
 * @param {string} param.inviteCode 邀请码
 * @param {string} param.queryUid 受邀人id，非空时校验不可被下家或自己邀请
 * @returns
 */
async function findUserByInviteCode({ inviteCode, queryUid } = {}) {
    if (typeof inviteCode !== 'string') {
        throw {
            errCode: ERROR.SYSTEM_ERROR,
        };
    }
    // 根据邀请码查询邀请人
    let getInviterRes;
    if (queryUid) {
        getInviterRes = await userCollection
            .where({
                _id: dbCmd.neq(queryUid),
                inviter_uid: dbCmd.not(dbCmd.all([queryUid])),
                my_invite_code: inviteCode,
            })
            .get();
    } else {
        getInviterRes = await userCollection
            .where({
                my_invite_code: inviteCode,
            })
            .get();
    }
    if (getInviterRes.data.length > 1) {
        // 正常情况下不可能进入此条件，以防用户自行修改数据库出错，在此做出判断
        throw {
            errCode: ERROR.SYSTEM_ERROR,
        };
    }
    const inviterRecord = getInviterRes.data[0];
    if (!inviterRecord) {
        throw {
            errCode: ERROR.INVALID_INVITE_CODE,
        };
    }
    return inviterRecord;
}

/**
 * 根据邀请码生成邀请信息
 * @param {object} param
 * @param {string} param.inviteCode 邀请码
 * @param {string} param.queryUid 受邀人id，非空时校验不可被下家或自己邀请
 * @returns
 */
async function generateInviteInfo({ inviteCode, queryUid } = {}) {
    const inviterRecord = await findUserByInviteCode({
        inviteCode,
        queryUid,
    });
    // 倒叙拼接当前用户邀请链
    const inviterUid = inviterRecord.inviter_uid || [];
    inviterUid.unshift(inviterRecord._id);
    return {
        inviterUid,
        inviteTime: Date.now(),
    };
}

/**
 * 检查当前用户是否可以接受邀请，如果可以返回用户记录
 * @param {string} uid
 */
async function checkInviteInfo(uid) {
    // 检查当前用户是否已有邀请人
    const getUserRes = await userCollection
        .doc(uid)
        .field({
            my_invite_code: true,
            inviter_uid: true,
        })
        .get();
    const userRecord = getUserRes.data[0];
    if (!userRecord) {
        throw {
            errCode: ERROR.ACCOUNT_NOT_EXISTS,
        };
    }
    if (userRecord.inviter_uid && userRecord.inviter_uid.length > 0) {
        throw {
            errCode: ERROR.CHANGE_INVITER_FORBIDDEN,
        };
    }
    return userRecord;
}

/**
 * 指定用户接受邀请码邀请
 * @param {object} param
 * @param {string} param.uid 用户uid
 * @param {string} param.inviteCode 邀请人的邀请码
 * @returns
 */
async function acceptInvite({ uid, inviteCode } = {}) {
    await checkInviteInfo(uid);
    const { inviterUid, inviteTime } = await generateInviteInfo({
        inviteCode,
        queryUid: uid,
    });

    if (inviterUid === uid) {
        throw {
            errCode: ERROR.INVALID_INVITE_CODE,
        };
    }

    // 更新当前用户的邀请人信息
    await userCollection.doc(uid).update({
        inviter_uid: inviterUid,
        invite_time: inviteTime,
    });

    // 更新当前用户邀请的用户的邀请人信息，这步可能较为耗时
    await userCollection
        .where({
            inviter_uid: uid,
        })
        .update({
            inviter_uid: dbCmd.push(inviterUid),
        });

    return {
        errCode: 0,
        errMsg: '',
    };
}

module.exports = {
    acceptInvite,
    generateInviteInfo,
    getValidInviteCode,
};
